package com.gitee.jdbc.mongodb;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.sql.Types;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import org.bson.Document;
import org.bson.types.ObjectId;

public class Util {

  private static final Pattern PATTERN_NUMBER = Pattern.compile("\\d+");
  private static final String SEPARATOR = ".";

  public static Object getByPath(Document document, String path) {
    String[] pathEls = path.split("\\.");
    Object cursor = document;
    int idx = 0;
    while (idx < pathEls.length && cursor instanceof Map) {
      cursor = ((Map) cursor).get(pathEls[idx]);
      idx++;
    }
    return (idx == pathEls.length) ? cursor : null;
  }

  public static int getJavaType(String bsonType) {
    switch (bsonType.toLowerCase()) {
      case "integer":
      case "int":
        return Types.INTEGER;
      case "double":
        return Types.DOUBLE;
      case "array":
        return Types.ARRAY;
      case "bool":
        return Types.BOOLEAN;
      case "date":
        return Types.DATE;
      case "null":
        return Types.NULL;
      case "dbPointer":
        return Types.DATALINK;
      case "javascript":
        return Types.JAVA_OBJECT;
      case "timestamp":
        return Types.TIMESTAMP;
      case "long":
        return Types.BIGINT;
      case "decimal":
        return Types.DECIMAL;
      case "minKey":
      case "maxKey":
        return Types.SMALLINT;
      case "objectId":
      case "regex":
      case "javascriptWithScope":
      case "string":
      case "symbol":
      default:
        return Types.VARCHAR;
    }
  }

  public static boolean allKeysAreNumbers(Map map) {
    if (map.isEmpty()) {
      return false;
    }
    for (Object key : map.keySet()) {
      boolean isNumber =
          key instanceof Number || (key instanceof String && PATTERN_NUMBER.matcher((String) key).matches());
      if (!isNumber) {
        return false;
      }
    }
    return true;
  }

  public static Class getListElementsClass(Object obj) {
    if (obj instanceof List) {
      List list = (List) obj;
      Class cls = null;
      for (Object val : list) {
        Class _cls = null;
        if (val instanceof Map) {
          _cls = Map.class;
        } else if (val instanceof Integer) {
          _cls = Integer.class;
        } else if (val instanceof Double) {
          _cls = Double.class;
        } else if (val instanceof Long) {
          _cls = Long.class;
        } else if (val instanceof Boolean) {
          _cls = Boolean.class;
        } else if (val instanceof Date) {
          _cls = Date.class;
        } else if (val instanceof String) {
          _cls = String.class;
        } else if (val instanceof ObjectId) {
          _cls = ObjectId.class;
        }
        if (cls == null) {
          cls = _cls;
        } else if (cls != _cls) {
          cls = Object.class;
        }
      }
      return cls;
    }
    return null;
  }

  public static String getBsonType(Document bsonDefinition) {
    Object bsonTypeObj = bsonDefinition.get("bsonType");
    if (bsonTypeObj instanceof List) {
      return String.valueOf(((List) bsonTypeObj).get(0));
    } else {
      return String.valueOf(bsonTypeObj);
    }
  }

  public static String readStringFromInputStream(InputStream is) throws IOException {
    if (is == null) {
      throw new IOException("Got empty Input Stream");
    }
    final BufferedReader in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
    final StringBuilder sb = new StringBuilder();
    String str;
    while (null != ((str = in.readLine()))) {
      if (sb.length() > 0) {
        sb.append("\n");
      }
      sb.append(str);
    }
    in.close();
    return sb.toString();
  }

  public static Map<String, Object> flatMap(Map<String, Object> source, String parentNode) {
    if (null == source) {
      return null;
    }
    Map<String, Object> flat = new LinkedHashMap<>();
    String prefix = Objects.nonNull(parentNode) ? parentNode + SEPARATOR : "";
    for (Map.Entry<String, Object> entry : source.entrySet()) {
      Object value = entry.getValue();
      String key = entry.getKey();
      String newKey = prefix + key;
      if (value instanceof Map) {
        flat.putAll(flatMap((Map) value, newKey));
      } else {
        flat.put(newKey, value);
      }
    }
    return flat;
  }

}
